#if !defined(_WIN32)

#include	"watcher.h"
#include	"logger.h"

#include	<sys/types.h>
#include	<sys/wait.h>
#include	<unistd.h>
#include	<fcntl.h>

#include	<csignal>
#include	<cstring>
#include	<cstdio>
#include	<memory>
#include	<iostream>
#include	<sstream>

static int nCpuIdx = -1;
static int nVszIdx = -1;
static int nResIdx = -1;

static void GetKeyIdx() {	
	static bool bCalc = false;
	if (bCalc) return;
	bCalc = true;

	FILE * pPipe = popen("ps -u -p 1", "r");
	if (!pPipe) return;

	std::string sResult;
	sResult.reserve(4096);

	char pBuf[2048] = {0};
	while (!feof(pPipe)) {
		size_t nSize = fread(pBuf, 1, 2048, pPipe);
		if (nSize > 0) sResult.append(pBuf, nSize);
	}

	std::istringstream issH(sResult);
	int nIdx = 0;

	while (issH.good()) {
		std::string sKey;
		issH >> sKey;
		if (sKey == "%CPU") nCpuIdx = nIdx;
		else if (sKey == "VSZ") nVszIdx = nIdx;
		else if (sKey == "RSS") nResIdx = nIdx;
		nIdx++;
	}

	pclose(pPipe);
}

Watcher & Watcher::Instance() {
	static std::unique_ptr<Watcher> iIns;
	if (!iIns) iIns.reset(new Watcher);
	GetKeyIdx();
	return *iIns;
}

void Watcher::Breath() {
	static time_t nLastBreath = 0;
	static char pBuf[2048] = {0};

	time_t nTick = time(NULL);
	if (nTick == nLastBreath) return;
	nLastBreath = nTick;

	Json::Value iMsg;
	iMsg["action"]		= "update";
	iMsg["watchers"]	= Json::Value(Json::arrayValue);	

	for (auto & kv : _mWatcher) {
		if (kv.second.emStatus == EStatus::Running) {
			int nStatus	= 0;
			int nChild	= waitpid(kv.second.nPid, &nStatus, WNOHANG);

			if (nChild > 0) {
				Tail(kv.first, kv.second);
				CatchExit(kv.first, kv.second, WEXITSTATUS(nStatus) == 0 ? EStatus::Stopped : EStatus::Fatal, true);
				continue;
			} else if (nChild == 0) {
				Tail(kv.first, kv.second);

				std::string sCmd = "ps -u -p " + std::to_string(kv.second.nPid);
				std::string sResult;

				sResult.reserve(1024 * 8);

				FILE * pPipe = popen(sCmd.data(), "r");
				if (!pPipe) continue;

				while (!feof(pPipe)) {
					size_t nSize = fread(pBuf, 1, 2048, pPipe);
					if (nSize > 0) sResult.append(pBuf, nSize);
				}

				pclose(pPipe);

				int nVsz = 0, nRes = 0, nIdx = 0;
				std::string sCpu = "";
				std::istringstream iss(sResult);

				while (iss.get() != '\n') continue;
				while (iss.good()) {
					std::string sData;
					iss >> sData;
					if (nIdx == nCpuIdx) {
						sCpu = sData;
					} else if (nIdx == nVszIdx) {
						nVsz = atoi(sData.data());
					} else if (nIdx == nResIdx) {
						nRes = atoi(sData.data());
					}

					nIdx++;
				}

				Json::Value iStatus;
				iStatus["name"]	= kv.first;
				iStatus["cpu"]	= sCpu;
				iStatus["vsz"]	= nVsz;
				iStatus["res"]	= nRes;

				iMsg["watchers"].append(iStatus);
			} else {
				CatchExit(kv.first, kv.second, errno == EINTR ? EStatus::Stopped : EStatus::Fatal, true);
			}
		}
	}

	if (iMsg["watchers"].size() > 0) Notify("/ws", iMsg);
}

void Watcher::Start(const std::string &sName, const std::string &sPath, const std::string &sCmd, int nRetry) {
	auto it = _mWatcher.find(sName);
	Info * pInfo = nullptr;

	if (it != _mWatcher.end() && it->second.emStatus == EStatus::Running) return;
	if (it == _mWatcher.end()) {
		Info iInfo;
		iInfo.nRetry	= nRetry;
		iInfo.sCmd		= sCmd;
		iInfo.sPath		= sPath;

		_mWatcher[sName] = iInfo;
		pInfo = &_mWatcher[sName];
	} else if (it->second.emStatus == EStatus::Running) {
		return;
	} else {
		pInfo = &it->second;
	}

	pInfo->nStart = time(NULL);

	std::vector<std::string> vParams;
	std::string sDeli = " \t";

	std::string::size_type nStart = 0, nEnd = 0;
	while (nEnd < sCmd.length()) {
		char nTemp = sCmd[nEnd];
		if (sDeli.find(nTemp) != std::string::npos) {
			if (nStart != nEnd) vParams.push_back(sCmd.substr(nStart, nEnd - nStart));
			++nEnd;
			nStart = nEnd;
		} else {
			++nEnd;
		}
	}

	if (nStart != nEnd)	vParams.push_back(sCmd.substr(nStart));
	if (vParams.size() <= 0) return;

	int pFd[2] = {0, 0};
	if (pipe(pFd) == -1) {
		LOG_ERR("Failed to create pipe for start watcher [%s]", sName.data());
		return;
	}

	pid_t nChildPid = fork();
	if (nChildPid == -1) {
		LOG_ERR("Start %s ... [Failed] Path : %s, Cmd : %s", sName.data(), sPath.data(), sCmd.data());
		pInfo->emStatus = EStatus::Fatal;
		pInfo->nPid = -1;
	} else if (nChildPid == 0) {	
		close(pFd[0]);
		dup2(pFd[1], STDERR_FILENO);
		dup2(pFd[1], STDOUT_FILENO);
		setpgid(nChildPid, nChildPid);
		
		if (-1 == chdir(sPath.data())) {
			std::cerr << "Failed to change directory : " << sPath << std::endl;
		} else {			
			char * pParam[20] = {0};
			memset(pParam, 0, sizeof(pParam));

			for (int i = 0; i < vParams.size() && i < 19; ++i)
				pParam[i] = (char *)vParams[i].data();

			if (-1 == execvp(vParams[0].data(), pParam))
				std::cerr << "No such file named : " << vParams[0] << std::endl;
		}

		exit(-1);
	} else {
		close(pFd[1]);
		int nFlags = fcntl(pFd[0], F_GETFL, 0);
		fcntl(pFd[0], F_SETFL, O_NONBLOCK | nFlags);

		pInfo->nFd = pFd[0];
		pInfo->nPid = nChildPid;
		pInfo->emStatus = EStatus::Running;

		LOG_INFO("Start %s ... [OK]. FD : %d, PID : %d", sName.c_str(), pInfo->nFd, pInfo->nPid);
	}

	Json::Value iMsg;
	iMsg["action"]		= "status_change";
	iMsg["watcher"]		= sName;
	iMsg["status"]		= pInfo->emStatus;
	iMsg["pid"]			= pInfo->nPid;
	iMsg["start_time"]	= pInfo->GetStartTime();

	AppendTail(sName, *pInfo, "\n\n----------- Start -----------\n");
	Notify("/ws", iMsg);
}

void Watcher::Stop(const std::string &sName) {
	auto it = _mWatcher.find(sName);
	if (it == _mWatcher.end() || it->second.emStatus != EStatus::Running) return;

	int nStatus = kill(it->second.nPid, SIGINT);
	if (nStatus == -1) {
		LOG_ERR("Stop %s ... [Failed]", sName.data());
	} else {
		nStatus = 0;		
		Tail(sName, it->second);
		while (waitpid(it->second.nPid, &nStatus, 0) == -1) {
			if (errno != EINTR) break;
		}

		CatchExit(sName, it->second, WEXITSTATUS(nStatus) == 0 ? EStatus::Stopped : EStatus::Fatal);
		LOG_INFO("Stop %s ... [OK]", sName.data());
	}
}

void Watcher::StopAll() {
	int nStatus = 0;

	for (auto & kv : _mWatcher) {
		if (kv.second.emStatus == EStatus::Running) {
			kill(kv.second.nPid, SIGINT);
			while (waitpid(kv.second.nPid, &nStatus, 0) == -1) {
				if (errno != EINTR) break;
			}
		}
	}
}

void Watcher::Remove(const std::string &sName) {
	auto it = _mWatcher.find(sName);
	if (it == _mWatcher.end()) return;

	if (it->second.emStatus == EStatus::Running) Stop(sName);
	_mWatcher.erase(it);
}

Watcher::Info * Watcher::Get(const std::string &sName) {
	auto it = _mWatcher.find(sName);
	if (it == _mWatcher.end()) return nullptr;
	return &it->second;
}

void Watcher::Notify(const std::string & sScope, const Json::Value & rMsg) {
	if (_fNotifier) _fNotifier(sScope, rMsg);
}

void Watcher::Tail(const std::string & sName, Info & rInfo) {
	static char pLine[8192] = {0}, pData[8192] = {0};

	memset(pLine, 0, 8192);
	memset(pData, 0, 8192);

	int nSize = read(rInfo.nFd, pLine, 8192);
	if (nSize > 0) {
		int nRead = 0;
		for (int i = 0; i < nSize; ++i) {
			if (pLine[i] == 0x1B && pLine[i + 1] == '[') {
				while (i < nSize && pLine[i] != 'm') i++;
			} else {
				pData[nRead] = pLine[i];
				++nRead;
			}
		}

		memcpy(rInfo.pTail + rInfo.nTail, pData, nRead);

		if (rInfo.nTail + nRead > 8192) {
			int nIdx = rInfo.nTail + nRead - 8192;
			int i = 0;

			for (; nIdx < rInfo.nTail + nRead; ++nIdx) {
				if (rInfo.pTail[nIdx] == '\n') {
					nIdx++;
					break;
				}
			}

			for (; i < rInfo.nTail + nRead - nIdx; ++i) {
				rInfo.pTail[i] = rInfo.pTail[i + nIdx];
			}

			rInfo.pTail[i] = '\0';
			rInfo.nTail = i;
		} else {
			rInfo.nTail += nRead;
		}

		Json::Value iMsg;
		iMsg["action"]	= "tail_append";
		iMsg["watcher"]	= sName;
		iMsg["data"]	= pData;

		Notify("/tail/" + sName, iMsg);
		Notify("/tail_all", iMsg);
	}
}

void Watcher::AppendTail(const std::string & sName, Info & rInfo, const std::string & sTail) {
	int nSize = sTail.size();
	memcpy(rInfo.pTail + rInfo.nTail, sTail.data(), nSize);

	if (rInfo.nTail + nSize > 8192) {
		int nIdx = rInfo.nTail + nSize - 8192;
		int i = 0;

		for (; nIdx < rInfo.nTail + nSize; ++nIdx) {
			if (rInfo.pTail[nIdx] == '\n') {
				nIdx++;
				break;
			}
		}

		for (; i < rInfo.nTail + nSize - nIdx; ++i) {
			rInfo.pTail[i] = rInfo.pTail[i + nIdx];
		}

		rInfo.pTail[i] = '\0';
		rInfo.nTail = i;
	} else {
		rInfo.nTail += nSize;
	}

	Json::Value iMsg;
	iMsg["action"]	= "tail_append";
	iMsg["data"]	= sTail;

	Notify("/tail/" + sName, iMsg);
}

void Watcher::CatchExit(const std::string & sName, Info & rInfo, EStatus emStatus, bool bRetry /* = false */) {
	close(rInfo.nFd);

	rInfo.emStatus	= emStatus;
	rInfo.nFd		= -1;
	rInfo.nPid		= -1;
	rInfo.nStart	= 0;

	Json::Value iMsg;
	iMsg["action"]		= "status_change";
	iMsg["watcher"]		= sName;
	iMsg["status"]		= rInfo.emStatus;
	iMsg["pid"]			= rInfo.nPid;
	iMsg["start_time"]	= rInfo.GetStartTime();

	if (bRetry && rInfo.nRetry > 0) {
		rInfo.emStatus	= EStatus::Retry;
		iMsg["status"]	= EStatus::Retry;
	
		Notify("/ws", iMsg);
		AppendTail(sName, rInfo, "\n----------- Auto-retry -----------\n");

		Start(sName, rInfo.sPath, rInfo.sCmd, rInfo.nRetry - 1);
	} else {
		Notify("/ws", iMsg);
		AppendTail(sName, rInfo, "\n----------- Stopped -----------\n");
	}
}

#endif